#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re

from util import safestr, safeunicode

rec = re.compile


## ---- Global data

HASH_PREFIX = '##hash##'


def get_lexer_by_name(name='FxWiki'):
    return globals()[name + 'Lexer']


## ---- Base Lexer Class

class Lexer(object):

    def set_formatter(self, formatter):
        pass

    def convert(self, stream):
        pass


## ---- FxWiki Lexer

class FxWikiLexer(Lexer):

    def __init__(self, formatter=None, converters=None, tabwidth=8):
        self.text = ''
        self.hash_context = {}
        self.tabwidth = tabwidth

        # Bind Formatter
        if formatter:
            self.bind_formatter(formatter)

        # Collect Converter
        self.converters = DEFAULT_CONVERTERS.copy()
        if converters:
            self.converters.update(converters)

    def bind_formatter(self, formatter):
        self.formatter = formatter
        self.formatter.bind_lexer(self)

    def _pretty_stream(self, stream):
        # Use unicode in internal.
        text = safeunicode(stream)

        # Handle newline.
        text = text.replace('\r\n', '\n').replace('\r', '\n')
        text = re.sub(r'\n{3,}', '\n\n', text)
        text = text.strip('\n')

        # Handle tab
        text = text.replace('\t', ' ' * self.tabwidth)

        # Handle EOF
        self.text = text + '\n'

        return text

    def convert(self, stream=''):
        # If file we read it to mem.
        if isinstance(stream, file):
            stream = stream.read(-1)

        out = []

        # Step 1: pretty stream.
        text = self._pretty_stream(stream)

        # Step 2: do_hash_encode for special text.
        text = self.do_hash_encode(text)

        # Step 3: do_block core func.
        for block in text.split('\n\n'):
            out.append( self.do_block( block.strip(' \n') ) )
        text = '\n\n'.join(out)

        # Step 4: do_hash_decode in reverse order.
        text = self.do_hash_decode(text)

        # Step 5: output it.
        text = re.sub(r'\n{3,}', '\n\n', text)

        return text

    def do_hash_encode(self, text):
        # Fetch hash rule.
        hash_lst = self.converters.get('hash', None)

        # If no hash just return it.
        if not hash_lst: return text

        # Do hash based on priority.
        hash_lst.sort()
        for priority, conv in hash_lst:
            for match in conv.match_regex.finditer(text):
                context = conv.build_context(match, text) # Build contex.

                # Tell Formatter to do hash with context.
                repl = self.formatter.do_hash(conv.name, context)

                # If Formatter return None we do nothing
                # or we save hased text and gen a short name
                # for it then replace raw text.
                if repl is not None:
                    repl = self.hash_helper(conv.name, repl)
                    if conv.is_block : # If block we add newline around.
                        repl = "\n%s\n" % repl.strip('\n')
                    text = conv.match_regex.sub(repl, text, 1)
        return text

    def do_hash_decode(self, text):
        # Fetch hash rule.
        hash_lst = self.converters.get('hash', None)

        # If no hash just return it.
        if not hash_lst: return text

        hash_lst.sort()
        hash_lst.reverse() # Decode in reverse order.
        for priority, conv in hash_lst:
            for match in conv.hash_regex.finditer(text):
                hash_id = match.groupdict().get('hash_id', None)
                if hash_id is not None: # Replace hash_id to hashed text.
                    hash_prog = self.hash_helper(conv.name)[int(hash_id)]
                    text = conv.hash_regex.sub(hash_prog, text, 1)
        return text

    def do_inline(self, text):
        hash_lst = self.converters.get('inline', None)
        if not hash_lst: return text

        # Do inline based on priority.
        hash_lst.sort()
        for priority, conv in hash_lst:
            for match in conv.match_regex.finditer(text):
                context = conv.build_context(match, text)
                # Tell Formatter do inline with context.
                repl = self.formatter.do_inline(conv.name, context)
                # If None we do nothing.
                if repl is not None:
                    text = conv.match_regex.sub(repl, text, 1)
        return text

    def do_block(self, text):
        hash_lst = self.converters.get('block', None)

        # If is a hashed block we ignore it.
        if (not hash_lst) or (text.startswith(HASH_PREFIX)): return text

        hash_lst.sort()
        for priority, conv in hash_lst:
            if conv.is_match(text):
                match = conv.match_regex.match(text)
                context = conv.build_context(match, text) # Build context.
                # Tell Formatter do block based on context.
                result = self.formatter.do_block(conv.name, context)
                # Handle every special action.
                if result == '#Action#continue':
                    continue
                elif result is not None:
                    if conv.need_inline: # Wheather do inline.
                        result = self.do_inline(result)
                    return result

        # Nothig match do default.
        text = self.formatter.do_block_default(text)
        # And then do inline in default block.
        return self.do_inline(text)

    def hash_helper(self, hash_name, value=None):
        hash_lst = self.hash_context.setdefault(hash_name, [0,])

        if value is None: # For lookup.
            return hash_lst

        # Gen hash_id.
        cur_index = self.hash_context[hash_name][0] = self.hash_context[hash_name][0] + 1

        # Save hashed text.
        self.hash_context[hash_name].append(value)

        return "%s@%s@%d" % (HASH_PREFIX, hash_name, cur_index)


## ---- Converter Class

class BaseConverter(object):
    converter_type = 'base'
    need_inline = True

    def __init__(self, **kw):
        self.name = kw.get('name', '')
        self.match_regex = kw.get('match_regex', rec(r''))

    def is_match(self, text):
        if self.match_regex.match(text):
            return True
        else:
            return False

    def build_context(self, match, text):
        context = match.groupdict().copy()
        context.setdefault('rawText', text)
        return context


class InlineConverter(BaseConverter):
    converter_type = 'inline'

    def __init__(self, **kw):
        BaseConverter.__init__(self, **kw)


class BlockConverter(BaseConverter):
    converter_type = 'block'

    def __init__(self, **kw):
        BaseConverter.__init__(self, **kw)


class HashConverter(BaseConverter):
    converter_type = 'hash'
    is_block = True

    def __init__(self, **kw):
        BaseConverter.__init__(self, **kw)
        reg_str = ('%s@%s@(?P<hash_id>\d+)' % (HASH_PREFIX, self.name))
        self.hash_regex = kw.get('hash_regex', rec(reg_str))


## ---- Converter Impl

class ListBlockConverter(BlockConverter):
    list_regex = rec(r'''\s*?(\*|#)\s+''')

    def __init__(self, **kw):
        kw['name'] = 'list'
        kw['match_regex'] = ListBlockConverter.list_regex
        BlockConverter.__init__(self, **kw)

    def build_context(self, match, text):
        context = BlockConverter.build_context(self, match, text)
        body = text.strip('\n')
        context['_list'] = [ e.strip('* ') for e in body.split('\n') ]
        return context


class TableBlockConverter(BlockConverter):
    table_regex = rec(r'''\s*?\|\|\s+''')

    def __init__(self, **kw):
        kw['name'] = 'table'
        kw['match_regex'] = TableBlockConverter.table_regex
        BlockConverter.__init__(self, **kw)

    def build_context(self, match, text):
        context = BlockConverter.build_context(self, match, text)
        body = text.strip('\n')
        lst = body.split('\n')
        context['table_header'] = [ e.strip() for e in lst[0].split('||') \
                if len(e.strip()) > 0 ]
        context['table_body'] = []
        if len(lst) > 1:
            for entry in lst[1:]:
                context['table_body'].append( [ e.strip() for e in \
                        entry.split('||') if len(e.strip()) > 0 ] )
        return context


class UnitHashConverter(HashConverter):
    converter_type = 'hash'
    is_block = True
    unit_regex = rec(r'''(?P<rawText>
        ^\s*\.\.\s+\{\{\{
        (?P<body>.*?)
        ^\s*\.\.\s+\}\}\}
    )''', re.X|re.M|re.S)

    def __init__(self, **kw):
        kw['name'] = 'unit'
        kw['match_regex'] = UnitHashConverter.unit_regex
        HashConverter.__init__(self, **kw)

    def build_context(self, match, text):
        context = HashConverter.build_context(self, match, text)
        context.update(self._simple_parse(context))
        return context

    def _simple_parse(self, context):
        text = context['body']
        context = {}
        first, rest = text.lstrip(' \n').split('\n', 1)
        a_lst = first.strip().split('::', 1)
        context['dynamic_name'] = a_lst[0].strip()
        if len(a_lst) > 1:
            b_lst = a_lst[1].strip()
            if b_lst:
                for elem in b_lst.split():
                    if ':' in elem:
                        k, v = elem.split(':')
                        context.setdefault('_attr', {})[k] = v
        context['content'] = rest.strip('\n')
        return context


class LinkHashConverter(HashConverter):
    converter_type = 'hash'
    is_block = True
    link_regex = rec(r'(?P<rawText>\[(?P<link>https?:.*?)(\|(?P<repl>.+?))?\])')

    def __init__(self, **kw):
        kw['name'] = 'link'
        kw['match_regex'] = LinkHashConverter.link_regex
        HashConverter.__init__(self, **kw)

    def build_context(self, match, text):
        context = HashConverter.build_context(self, match, text)
        context.update(self._simple_parse(context))
        return context

    def _simple_parse(self, context):
        _ctx = {}
        IMGURLSTR = rec(r'.+((\.[Pp][Nn][Gg])|(\.[Gg][Ii][Ff])|(\.[Jj][Pp][Ee]?[Gg]))')
        if not context['repl']:
            _ctx['type'] = 'url'
            if IMGURLSTR.match(context['link']):
                _ctx['type'] = 'img'
        else:
            _ctx['type'] = 'url_text'
            if IMGURLSTR.match(context['repl']):
                _ctx['type'] = 'url_img'
        return _ctx



## --- Default Converter

DEFAULT_CONVERTERS = {
    'inline' : [
        ( 1010, InlineConverter( name='italic', match_regex=rec(r'_(?P<italic>[^_]+)_') ) ),
        ( 1020, InlineConverter( name='bold', match_regex=rec(r'\*(?P<bold>[^\*]+)\*') ) ),
        ( 1030, InlineConverter( name='monospace', match_regex=rec(r'`(?P<monospace>[^`]+)`') ) ),
        ( 1040, InlineConverter( name='highlight', match_regex=rec(r'!(?P<highlight>[^!]+)!') ) ),
    ],

    'block' : [
        ( 2010, BlockConverter( name='header', match_regex=rec(r'^\s*?(?P<level>[=]+)\s+(?P<body>.+?)\s+(\1)\s*?$') ) ),
        ( 2020, BlockConverter( name='blockquote', match_regex=rec(r'\s*?>>>') ) ),
        ( 2030, ListBlockConverter() ),
        ( 2040, TableBlockConverter() ),
    ],

    'hash' : [
        ( 3010, UnitHashConverter() ),
        ( 3020, LinkHashConverter() ),
    ],
}



## ---- mainline

if __name__ == '__main__':
    pass
